<?php
/**
 * Created by PhpStorm.
 * User: guokunsheng
 * Date: 2019/12/23
 * Time: 11:54
 */

namespace App\Tools\Astrolog;

use App\Models\Api\AspectChange;
use App\Models\Api\AstroChange as AstroChangeModel;
use App\Models\Api\SyzygyTIme;
use Exception;
use Log;

/**
 * 获取星象变动数据
 * Class Calendar
 * @package App\Tools\Astrolog
 */
class Calendar
{
    /**
     * 月食数据
     * @var null
     */
    protected $lunarEclipseTime;
    /**
     * 日食数据
     * @var null
     */
    protected $solarEclipseTime;
    /**
     * 新满月点数据
     * @var null
     */
    protected $syzygyTime;

    /**
     * 需要获取的数据开关 设置为false表示不获取该数据
     * @var array
     */
    protected $setting = [
        1 => false,// 行星换座
        2 => false,// 行星顺逆行切换（1：顺行、2：逆行）
        3 => false,// 日食
        4 => false,// 月食
        5 => false,// 新月（0˚）
        6 => false,// 满月（180˚）
        7 => true,// 相位
    ];

    /**
     * 获取数据变动数据
     * @param int $year 起始年份 （北京时间）
     * @param int $month 起始月份 （北京时间）
     * @param int $day 起始日   （北京时间）
     */
    public function get($year = 2020, $month = 1, $day = 1)
    {
        $advance = [
            'year' => input('year/d', $year),
            'month' => input('month/d', $month),
            'day' => input('day/d', $day),
            'hour' => 0,
            'minute' => 0,
            'second' => 0,
            'timezone' => 8,
            'is_summer' => 0,
        ];

        $Astrolog = Astrolog::getInstance();
        $Astrolog->setAdvance($advance)->setExecPrefix(config('astrolog.prefix'));

        $utAdvance = $Astrolog->getAdvance();
        if ($this->setting[4])
        {
            // 获取下次月食时间点
            $this->lunarEclipseTime = $this->getEclipse(1, $utAdvance);
        }
        if ($this->setting[3])
        {
            // 获取下次日食时间点
            $this->solarEclipseTime = $this->getEclipse(0, $utAdvance);
        }
        // 从数据库获取下次新、满月时间点
        $date = date('Y-m-d H:i:s', $utAdvance['ut_timestamp']);
        if ($this->setting[5] || $this->setting[6])
        {
            $this->syzygyTime = $this->getSyzygyTime($date);
        }

        if ($this->setting[1] || $this->setting[2] || $this->setting[3] || $this->setting[4] || $this->setting[5] || $this->setting[6])
        {
            $AstroChangeModel = new AstroChangeModel();
            $useTime = $utAdvance['ut_timestamp'];
            $preData = [];
            // 计算多少天的数据
            for ($l = 0; $l < 10000; $l++)
            {
                $result = [];
                echo '北京时间：' . date('Y-m-d', strtotime('+8 hour', $useTime)) . '计算开始' . PHP_EOL;
                // 遍历48个1800秒数据
                for ($i = 0; $i < 48; $i++)
                {
                    $st1 = microtime(true);
                    // 获取半小时内所有行星的度数变化  总计18000条数据  对应
                    $utData = $Astrolog->getPlanetDegByTimestamp('0123456789', $useTime, 1800, '1s', 'ls');
                    $st2 = microtime(true);
                    // 遍历1800秒中每秒的数据
                    for ($j = 0; $j < 1800; $j++)
                    {
                        $tmpData = array_slice($utData, $j * 10, 10);

                        $iTimeData = $this->getTimeData($tmpData);

                        if (empty($preData))
                        {
                            $preData = $iTimeData;
                        } else
                        {
                            $this->getChanges($useTime, $preData, $iTimeData, $result);
                        }
                        $useTime++;
                    }
                    $st3 = microtime(true);
                    echo '北京时间：' . date('Y-m-d H:i:s', strtotime('+8 hour', $useTime)) . '获取行星数据耗时：' . ($st2 - $st1) . '计算耗时：' . ($st3 - $st2) . PHP_EOL;
                }
                $localTime = date('Y年m月d日', strtotime('+8 hour', $useTime - 1));
                echo '北京时间：' . $localTime . '计算结束 数据：' . json_encode($result) . PHP_EOL;
                // 每天生成的数据写入数据库
                try
                {
                    $AstroChangeModel->saveAll($result);
                    Log::write('成功写入' . $localTime . '数据');
                }
                catch (\Exception $e)
                {
                    Log::write('Error:' . $e->getMessage());
                    echo '写入失败:' . $e->getMessage() . PHP_EOL;
                }
                usleep(100);
            }
        }
        /**
         * 每分钟计算  获取相位大致时间
         * 精确到秒 获取精确成相时间、入相时间、结束成相时间
         */
        if ($this->setting[7])
        {
            $useTime = $utAdvance['ut_timestamp'];
            $preData = [];
            $max = 1440; // 一天有1440分钟
            $days = abs(strtotime($year . '-' . $month . '-' . $day) - strtotime('2100-01-01')) / 86400;
            // 计算多少天的数据
            for ($l = 0; $l < $days; $l++)
            {
                $result = [];
                echo '北京时间：' . date('Y-m-d', strtotime('+8 hour', $useTime)) . ' 相位计算开始' . PHP_EOL;

                $st1 = microtime(true);
                // 遍历9大行星（由于最后一颗行星已经计算了和其他行星的成相，所以此处仅前9个）
                for ($i = 0; $i < 9; $i++)
                {
                    for ($j = $i + 1; $j <= 9; $j++)
                    {
                        $this->caclAspectTime($result, $i, $j, $useTime, $max);
                    }
                }
                $useTime += 86400;
                $st3 = microtime(true);
                echo '北京时间：' . date('Y-m-d H:i:s', strtotime('+8 hour', $useTime)) . ' 计算耗时：' . ($st3 - $st1) . PHP_EOL;
                $localTime = date('Y年m月d日', strtotime('+8 hour', $useTime - 1));
//                echo '北京时间：' . $localTime . ' 计算结束数据：' . json_encode($result) . PHP_EOL;
                // 每天生成的数据写入数据库
                if (!empty($result))
                {
                    try
                    {
                        (new AspectChange())->saveAll($result);
                        Log::write('成功写入' . $localTime . '数据');
                    }
                    catch (\Exception $e)
                    {
                        Log::write('Error:' . $e->getMessage());
                        echo '写入失败:' . $e->getMessage() . PHP_EOL;
                    }
                }
                usleep(100);
            }
        }
    }

    /**
     * 获取天象变动数据
     * 行星：换座                                17:37 火星进射手
     * 水金火木土天海冥：顺行 or 逆行 切换           9:48天王星在金牛座顺行  十大行星除日月之外的
     * 星座：日月食                               1:08巨蟹座月食
     * 星座：新月满月                             3:21巨蟹座满月
     * @param $timestamp int 当前数据的使用的时间戳
     * @param $preData array 前一组数据，用于比对是否发生改变
     * @param $nowData array 当前数据，与前一组数据进行对比
     * @param $result array
     */
    protected function getChanges($timestamp, &$preData, $nowData, &$result)
    {
        $utAdvance = [
            'ut_date' => strftime('%d.%m.%Y', strtotime('+1 day', $timestamp))
        ];
        foreach ($nowData as $k => $v)
        {
            $dateTime = date('Y-m-d H:i:s', $timestamp);
            // 检测是否换座 不计算月亮
            if ($this->setting[1] && $k !== 1 && $v['in_sign_id'] !== $preData[$k]['in_sign_id'])
            {
                $result[] = [
                    'type' => 1,
                    'time' => $dateTime,
                    'pid' => $v['id'],
                    'in_sign_id' => $v['in_sign_id'],
                    'speed' => 0,
                ];
                $preData[$k]['in_sign_id'] = $v['in_sign_id'];
            }

            // 检测顺行 or 逆行切换
            $npSpeed = $v['speed'] * $preData[$k]['speed'];
            if ($this->setting[2] && $k > 1 && $npSpeed < 0)
            {
                if ($v['speed'] > 0)
                {
                    $speedType = 1;
                } elseif ($v['speed'] < 0)
                {
                    $speedType = 2;
                } else
                {
                    $speedType = 0;
                }
                $result[] = [
                    'type' => 2,
                    'time' => $dateTime,
                    'pid' => $v['id'],
                    'in_sign_id' => $v['in_sign_id'],
                    'speed' => $speedType,
                ];
                $preData[$k]['speed'] = $v['speed'];
            }

            // 检测发生日、月食星座
            if ($this->setting[3] && $k === 0 && $timestamp === $this->solarEclipseTime['start'])
            {
                $solarTime = ['max' => $this->solarEclipseTime['max'], 'end' => $this->solarEclipseTime['end']];
                $result[] = [
                    'type' => 3,
                    'time' => $dateTime,// 日食开始时间
                    'pid' => $v['id'],
                    'in_sign_id' => $v['in_sign_id'],
                    'speed' => 0,
                    'extend' => json_encode($solarTime),
                ];
                // 获取下次日食时间点
                $this->solarEclipseTime = $this->getEclipse(0, $utAdvance);
            }

            if ($this->setting[4] && $k === 1 && $timestamp === $this->lunarEclipseTime['start'])
            {
                $lunarTime = ['max' => $this->lunarEclipseTime['max'], 'end' => $this->lunarEclipseTime['end']];
                $result[] = [
                    'type' => 4,
                    'time' => $dateTime,// 月食开始时间
                    'pid' => $v['id'],
                    'in_sign_id' => $v['in_sign_id'],
                    'speed' => 0,
                    'extend' => json_encode($lunarTime),
                ];
                // 获取下次月食时间点
                $this->lunarEclipseTime = $this->getEclipse(1, $utAdvance);
            }
            // 检测发生新、满月星座
            if (($this->setting[5] || $this->setting[6]) && $k === 1 && $timestamp === $this->syzygyTime['time'])
            {
                $phase = (int)$this->syzygyTime['phase'];
                if ($this->setting[5] && $phase === 0)
                {
                    // 新月 0˚
                    $result[] = [
                        'type' => 5,
                        'time' => $dateTime,
                        'pid' => $v['id'],
                        'in_sign_id' => $v['in_sign_id'],
                        'speed' => 0,
                    ];
                } elseif ($this->setting[6] && $phase === 180)
                {
                    // 满月 180˚
                    $result[] = [
                        'type' => 6,
                        'time' => $dateTime,
                        'pid' => $v['id'],
                        'in_sign_id' => $v['in_sign_id'],
                        'speed' => 0,
                    ];
                }
                // 获取下次新、满月时间点
                $this->syzygyTime = $this->getSyzygyTime(date('Y-m-d H:i:s', $timestamp));
            }

            // 检测十大行星间的相位数据
            if ($this->setting[7])
            {
                $this->getAspects($nowData, $result);
            }
        }
    }

    /**
     * 获取当前时间行星数据格式化后的格式
     * @param array $planetDegs 当前行星数据数组
     * @return array
     */
    protected function getTimeData($planetDegs = [])
    {
        $result = [];
        if (!empty($planetDegs))
        {
            foreach ($planetDegs as $k => $planet)
            {
                [$deg, $speed] = explode(',', $planet);

                $deg *= 1;

                $speed *= 1;
                // 计算落座ID
                $in_sign = (int)ceil($deg / 29.999999999999);

                if ($in_sign > 12)
                {
                    $in_sign = 12;
                } elseif ($in_sign < 1)
                {
                    $in_sign = 1;
                }
                $result[] = [
                    'id' => $k,// 行星id
                    'deg' => $deg,// 行星度数
                    'in_sign_id' => $in_sign,// 行星落座
                    'speed' => $speed// 行星速度
                ];
            }
        }
        return $result;
    }

    /**
     * 获取日食月食时间 0时区
     * @param int $type
     * @param array $timeinfo
     * @return array
     */
    protected function getEclipse($type = 1, $timeinfo = [])
    {
        $astrologPrefix = config('astrolog.prefix');
        // 月食
        if ($type === 1)
        {
            $exec_str = $astrologPrefix . " -b{$timeinfo['ut_date']} -lunecl -eswe -g, -head";
        }// 日食
        else
        {
            $exec_str = $astrologPrefix . " -b{$timeinfo['ut_date']} -solecl -eswe -g, -head";
        }
        exec($exec_str, $out);
        $arr = explode(',', $out[1]);
        $maxTime = $this->calcTime(trim($arr[1]), trim($arr[2]));
        $tmp = explode(',', $out[2]);
        if ($type === 1)
        {
            $startTime = $this->calcTime(trim($arr[1]), trim($tmp[1]));// 月食开始时间
            $endTime = $this->calcTime(trim($arr[1]), trim($tmp[6]));// 月食开始时间
        } else
        {
            $startTime = $this->calcTime(trim($arr[1]), trim($tmp[1]));// 日食开始时间
            $endTime = $this->calcTime(trim($arr[1]), trim($tmp[4]));// 日食开始时间
        }
        // 返回0时区时间
        return ['start' => $startTime, 'end' => $endTime, 'max' => $maxTime];
    }

    /**
     * 日食月食时间处理
     * @param $date string 10.01.2020
     * @param $time string 19:10:06.2
     * @return false|int
     */
    private function calcTime($date, $time)
    {
        $time = substr($time, 0, -2);
        $timeArr = explode(':', $time);
        $time = $timeArr[0] * 1 . '.' . $timeArr[1] * 1 . '.' . $timeArr[2] * 1;
        return strtotime($date . ' ' . $time);
    }

    /**
     * 查询下一个新满月的时间点 0时区
     * @param string $time
     * @return array
     */
    protected function getSyzygyTime($time = '2019-01-01 00:00:00')
    {
        try
        {
            $data = (new SyzygyTIme())->field('time,phase,deg')->where('time', '>', $time)->findOrFail()->toArray();

            $data['time'] = strtotime($data['time']);
        }
        catch (Exception $e)
        {
            $data = [];
        }
        return $data;
    }

    /**
     * 计算开始成相时间点
     * 计算精准成相时间点
     * 计算结束成相时间点
     * @param array $astroData
     * @param $result
     * @return void
     */
    protected function getAspects(array $astroData, &$result): void
    {
        $min = 0;
        $max = 9;
        for ($i = $min; $i < $max; $i++)
        {
            for ($j = $i + 1; $j < $max + 1; $j++)
            {
                // 计算两个星体的速度差值 大于0 =>则i比j速度要快  小于0 =>则i比j速度要慢
//                $spd = abs($astroData[$i]['speed']) - abs($astroData[$j]['speed']);
                $da = abs($astroData[$i]['deg'] - $astroData[$j]['deg']);
                if ($da > 180) $da = 360 - $da;

                $da0 = abs($da - 0);
                $da60 = abs($da - 60);
                $da90 = abs($da - 90);
                $da120 = abs($da - 120);
                $da180 = abs($da - 180);
                $orb = 0.00005;

                if ($da0 <= $orb)
                {
//                    'type' => 4,
//                    'time' => $dateTime,// 月食开始时间
//                    'pid' => $v['id'],
//                    'in_sign_id' => $v['in_sign_id'],
//                    'speed' => 0,
//                    'extend' => json_encode($lunarTime),
                    $result[] = [
                        'id1' => $i,
                        'id2' => $j,
                        'phase_e' => 0,
                        'orb' => $da0,
                    ];
                } elseif ($da60 <= $orb)
                {
                    $result[] = [
                        'id1' => $i,
                        'id2' => $j,
                        'phase_e' => 60,
                        'orb' => $da60,
                    ];
                } elseif ($da90 <= $orb)
                {
                    $result[] = [
                        'id1' => $i,
                        'id2' => $j,
                        'phase_e' => 90,
                        'orb' => $da90,
                    ];
                } elseif ($da120 <= $orb)
                {
                    $result[] = [
                        'id1' => $i,
                        'id2' => $j,
                        'phase_e' => 120,
                        'orb' => $da120,
                    ];
                } elseif ($da180 <= $orb)
                {
                    $result[] = [
                        'id1' => $i,
                        'id2' => $j,
                        'phase_e' => 120,
                        'orb' => $da120,
                    ];
                }
            }
        }
    }

    protected function caclAspectTime(&$result, $i, $j, $useTime, $max, $deg = null, $step = '1m')
    {
        // 天象相位容许度 仅检测主要相位
//        $aspOrbs = [0 => 2, 60 => 1, 90 => 2, 120 => 2, 180 => 2];
        // 天象与三限现代相位容许度一致，使用64555
        $aspOrbs = [0 => 6, 60 => 4, 90 => 5, 120 => 5, 180 => 5];

        $Astrolog = Astrolog::getInstance();
        /**
         * 获取行星间的距离，用来计算成相时间
         */
        $utData = $Astrolog->getPlanetDegDistanceByTimestamp($j, $i, $useTime, $max, $step, 'l');

        $b = $step === '1m' ? 60 : 1;
        foreach ($utData as $k => $v)
        {
            $diff = abs((float)$v); // 黄道经度度数差 有正数和负数
            // 遍历主要相位容许度配置
            foreach ($aspOrbs as $asp => $orb)
            {
                if ($deg !== null && $asp !== $deg)
                {
                    continue;
                }
                $nowOrb = $diff > $asp ? $diff - $asp : $asp - $diff;
                $pre = isset($utData[$k - 1]) ? abs((float)($utData[$k - 1])) : -999;
                $preOrb = $pre > $asp ? $pre - $asp : $asp - $pre;
                $caclTime = $useTime + $k * $b;
                // 在成相范围内
                if ($diff <= $asp + $orb && $diff >= $asp - $orb)
                {
                    if ($pre !== -999)
                    {
                        // 若上一个不成相，则当前为入相
                        if ($pre > $asp + $orb || $pre < $asp - $orb)
                        {
                            // 获取前60s当前分钟60s
                            if ($b === 1)
                            {
                                $result[] = [
                                    'type' => 7,
                                    'pid1' => $i,
                                    'pid2' => $j,
                                    'phase' => $asp,
                                    'time' => date('Y-m-d H:i:s', $caclTime),
                                    'state' => 1, // 入相
                                ];
                                echo '北京时间：' . date('Y-m-d H:i:s', strtotime('+8 hour', $caclTime)) . ' ', $i . '与' . $j . '开始成相' . $asp . PHP_EOL;
                            } elseif ($b === 60)
                            {
                                 $this->caclAspectTime($result, $i, $j, $caclTime - $b, 120, $asp, '1s');
                            }
                        } else
                        {
                            $next = isset($utData[$k + 1]) ? abs((float)($utData[$k + 1])) : -999;
                            $nextOrb = $next > $asp ? $next - $asp : $asp - $next;
                            // 上一个成相，下一个也成相，且 当前容许最小
                            if ($next !== -999)
                            {
                                if (($asp === 180 && $diff > $pre && $diff > $next) || ($asp !== 180 && $nowOrb < $preOrb && $nowOrb < $nextOrb))
                                {
                                    if ($b === 1)
                                    {
                                        $result[] = [
                                            'type' => 7,
                                            'pid1' => $i,
                                            'pid2' => $j,
                                            'phase' => $asp,
                                            'time' => date('Y-m-d H:i:s', $caclTime),
                                            'state' => 2, // 精准成相
                                        ];
                                        echo '北京时间：' . date('Y-m-d H:i:s', strtotime('+8 hour', $caclTime)) . ' ', $i . '与' . $j . '精准成相' . $asp . PHP_EOL;
                                    } elseif ($b === 60)
                                    {
                                        $this->caclAspectTime($result, $i, $j, $caclTime - $b, 120, $asp, '1s');
                                    }
                                }
                            }
                        }
                    }
                } else
                {
                    // 当前不在成相范围内
                    // 若上一个成相，则当前为结束成相
                    if ($pre !== -999 && $pre <= $asp + $orb && $pre >= $asp - $orb)
                    {
                        if ($b === 1)
                        {
                            $result[] = [
                                'type' => 7,
                                'pid1' => $i,
                                'pid2' => $j,
                                'phase' => $asp,
                                'time' => date('Y-m-d H:i:s', $caclTime),
                                'state' => 3, // 结束成相
                            ];
                            echo '北京时间：' . date('Y-m-d H:i:s', strtotime('+8 hour', $caclTime)) . ' ', $i . '与' . $j . '结束成相' . $asp . PHP_EOL;
                        } elseif ($b === 60)
                        {
                            $this->caclAspectTime($result, $i, $j, $caclTime - $b, 120, $asp, '1s');
                        }
                    }
                }
            }
        }
    }

    /**
     * 将数据转换为可阅读格式
     * @param array $data
     * @return array
     */
    public function transerData($data = [])
    {
        $planets = ['太阳', '月亮', '水星', '金星', '火星', '木星', '土星', '天王星', '海王星', '冥王星'];
        $signs = ['', '白羊座', '金牛座', '双子座', '巨蟹座', '狮子座', '处女座', '天秤座', '天蝎座', '射手座', '摩羯座', '水瓶座', '双鱼座'];
        $result = [];
        if (!empty($data))
        {
            foreach ($data as $d)
            {
//                $text = date('Y-m-d H:i:s', strtotime($d['time']) + 3600 * 8) . ' ';
                $item = [];
                $item['time'] = date('H:i', strtotime($d['time']) + 3600 * 8) . ' ';

                switch ($d['type'])
                {
                    case 1:
                        $item['text'] = $planets[$d['pid']] . '进入' . $signs[$d['in_sign_id']];
                        break;
                    case 2:
                        $item['text'] = $planets[$d['pid']] . '在' . $signs[$d['in_sign_id']] . ((int)$d['speed'] === 1 ? '顺行' : '逆行');
                        break;
                    case 3:
                        $item['text'] = $signs[$d['in_sign_id']] . '日食';
                        break;
                    case 4:
                        $item['text'] = $signs[$d['in_sign_id']] . '月食';
                        break;
                    case 5:
                        $item['text'] = $signs[$d['in_sign_id']] . '新月';
                        break;
                    case 6:
                        $item['text'] = $signs[$d['in_sign_id']] . '满月';
                        break;
                    default:
                        $item['text'] = '';
                        break;
                }
                $result[] = $item;
            }
        }
        return $result;
    }
}